use crate::shared::interpolation;
use crate::shared::low_pass_filter::LowPassFilter;
use crate::simulation::{
    InitContext, SimulationElement, SimulatorWriter, UpdateContext, VariableIdentifier, Write,
};
use uom::si::{
    angle::{degree, radian},
    angular_acceleration::radian_per_second_squared,
    angular_velocity::{radian_per_second, revolution_per_minute},
    area::square_meter,
    f64::*,
    mass_density::kilogram_per_cubic_meter,
    power::watt,
    ratio::ratio,
    thermodynamic_temperature::kelvin,
    torque::newton_meter,
    velocity::knot,
    velocity::meter_per_second,
};

use std::time::Duration;

pub struct WindTurbine {
    rpm_id: VariableIdentifier,
    angular_position_id: VariableIdentifier,
    propeller_angle_id: VariableIdentifier,

    propeller_radius: Length,
    propeller_area: Area,

    tip_speed: Velocity,
    tip_speed_ratio: Ratio,

    position: Angle,
    speed: AngularVelocity,
    acceleration: AngularAcceleration,
    torque_sum: Torque,

    propeller_beta_pitch: LowPassFilter<Ratio>,

    rpm_governor_breakpoints: [f64; 9],
    propeller_pitch_ratio_to_rpm_map: [f64; 9],

    dynamic_friction_coefficient: f64,
    best_efficiency_tip_speed_ratio: f64,
    propeller_inertia: f64,
}
impl WindTurbine {
    // Stow position from which mechanical pin unlocks blades rotation
    const PIN_BLADE_UNLOCK_POSITION_WITH_STOW_RATIO: f64 = 0.8;

    // Mach speed boundaries for tip speed. Efficiency will start to drop at MIN value to be null at MAX
    const MAX_TIP_SPEED_MACH_NUMBER: f64 = 1.01;
    const MIN_TIP_SPEED_MACH_NUMBER: f64 = 0.95;

    // Max absolute aerodynamic efficiency
    const MAX_AERODYNAMIC_EFFICIENCY: f64 = 0.45;

    const PROPELLER_PITCH_MECHANISM_TIME_CONSTANT: Duration = Duration::from_millis(300);

    // Low speed special calculation threshold. Under that value we compute torque and friction differently
    const LOW_SPEED_PHYSICS_ACTIVATION: f64 = 100.;

    // Coefficient to adjust force generated by wind on an almost steady blade
    const LOW_SPEED_WIND_FORCE_COEFFICIENT: f64 = 0.003;

    const CUT_OUT_WIND_SPEED_KTS: f64 = 45.;

    const SPECIFIC_HEAT_RATIO_FOR_AIR: f64 = 1.4;
    const GAS_CONSTANT_FOR_AIR: f64 = 287.;

    // Force coming from either pump plunger position or generator magnetic poles position
    const PUMPING_TORQUE_COEFFICIENT_NM: f64 = 2.;
    // Number of pump plungers or magnetic poles
    const NUMBER_OF_MAX_PUMPING_POSITION_PER_REVOLUTION: f64 = 5.;

    pub fn new(
        context: &mut InitContext,
        propeller_radius: Length,
        rpm_governor_breakpoints: [f64; 9],
        propeller_alpha_to_rpm_map: [f64; 9],
        dynamic_friction_coefficient: f64,
        best_efficiency_tip_speed_ratio: f64,
        propeller_inertia: f64,
    ) -> Self {
        Self {
            rpm_id: context.get_identifier("RAT_RPM".to_owned()),
            angular_position_id: context.get_identifier("RAT_ANGULAR_POSITION".to_owned()),
            propeller_angle_id: context.get_identifier("RAT_PROPELLER_ANGLE".to_owned()),

            propeller_radius,
            propeller_area: propeller_radius * propeller_radius * std::f64::consts::PI,

            tip_speed: Velocity::default(),
            tip_speed_ratio: Ratio::default(),

            position: Angle::default(),
            speed: AngularVelocity::default(),
            acceleration: AngularAcceleration::default(),
            torque_sum: Torque::default(),

            propeller_beta_pitch: LowPassFilter::new(Self::PROPELLER_PITCH_MECHANISM_TIME_CONSTANT),

            rpm_governor_breakpoints,
            propeller_pitch_ratio_to_rpm_map: propeller_alpha_to_rpm_map,

            dynamic_friction_coefficient,
            best_efficiency_tip_speed_ratio,
            propeller_inertia,
        }
    }

    fn update_tip_speed_ratio(&mut self, context: &UpdateContext) {
        self.tip_speed = self.propeller_radius * self.speed();

        let wind_speed = context.true_airspeed();

        self.tip_speed_ratio = if wind_speed.get::<meter_per_second>().abs() > 0.001 {
            (self.tip_speed / wind_speed).abs()
        } else {
            Ratio::default()
        }
    }

    fn mach_effect_ratio(&self, context: &UpdateContext) -> Ratio {
        let speed_of_sound = Velocity::new::<meter_per_second>(
            (Self::SPECIFIC_HEAT_RATIO_FOR_AIR
                * Self::GAS_CONSTANT_FOR_AIR
                * context.ambient_temperature().get::<kelvin>())
            .sqrt(),
        );

        let blade_tip_mach_number = self.tip_speed / speed_of_sound;

        ((Ratio::new::<ratio>(Self::MAX_TIP_SPEED_MACH_NUMBER) - blade_tip_mach_number)
            / (Ratio::new::<ratio>(Self::MAX_TIP_SPEED_MACH_NUMBER)
                - Ratio::new::<ratio>(Self::MIN_TIP_SPEED_MACH_NUMBER)))
        .min(Ratio::new::<ratio>(1.))
        .max(Ratio::new::<ratio>(0.))
    }

    fn power_efficiency(&self) -> Ratio {
        Ratio::new::<ratio>(
            (Self::MAX_AERODYNAMIC_EFFICIENCY
                * (1. - self.propeller_beta_pitch.output().get::<ratio>()))
                / (1.
                    + 3. * (self.tip_speed_ratio.get::<ratio>()
                        - self.best_efficiency_tip_speed_ratio)
                        .powi(2)),
        )
    }

    fn wind_power_available(&self, context: &UpdateContext) -> Power {
        if context.true_airspeed().get::<knot>().abs() > Self::CUT_OUT_WIND_SPEED_KTS {
            0.5 * context.ambient_air_density()
                * self.propeller_area
                * context.true_airspeed()
                * context.true_airspeed()
                * context.true_airspeed()
        } else {
            Power::default()
        }
    }

    fn wind_power_converted(&self, context: &UpdateContext) -> Power {
        self.wind_power_available(context)
            * self.power_efficiency()
            * self.mach_effect_ratio(context)
    }

    pub fn speed(&self) -> AngularVelocity {
        self.speed
    }

    pub fn position(&self) -> Angle {
        self.position
    }

    fn is_low_speed(&self) -> bool {
        self.speed.get::<revolution_per_minute>().abs() < Self::LOW_SPEED_PHYSICS_ACTIVATION
    }

    fn update_generated_torque(&mut self, context: &UpdateContext) {
        let cur_beta_pitch_ratio = interpolation(
            &self.rpm_governor_breakpoints,
            &self.propeller_pitch_ratio_to_rpm_map,
            self.speed().get::<revolution_per_minute>(),
        );

        self.propeller_beta_pitch
            .update(context.delta(), Ratio::new::<ratio>(cur_beta_pitch_ratio));

        let generated_torque = if !self.is_low_speed() {
            Torque::new::<newton_meter>(
                self.wind_power_converted(context).get::<watt>()
                    / self.speed().get::<radian_per_second>().abs(),
            )
        } else {
            Torque::new::<newton_meter>(
                Self::LOW_SPEED_WIND_FORCE_COEFFICIENT
                    * context
                        .ambient_air_density()
                        .get::<kilogram_per_cubic_meter>()
                    * self.propeller_area.get::<square_meter>()
                    * context.true_airspeed().get::<meter_per_second>().powi(2)
                    * context.true_airspeed().get::<meter_per_second>().signum(),
            )
        };

        self.torque_sum += generated_torque;
    }

    fn update_friction_torque(&mut self, resistant_torque: Torque) {
        let friction_torque = if self.is_low_speed() {
            Torque::new::<newton_meter>(self.speed().get::<radian_per_second>() * -1.)
        } else {
            Torque::new::<newton_meter>(
                self.speed().get::<radian_per_second>() * -self.dynamic_friction_coefficient,
            )
        };

        let pumping_torque = Torque::new::<newton_meter>(
            Self::PUMPING_TORQUE_COEFFICIENT_NM
                * (Self::NUMBER_OF_MAX_PUMPING_POSITION_PER_REVOLUTION
                    * self.position.get::<radian>())
                .cos(),
        );

        self.torque_sum += resistant_torque + friction_torque + pumping_torque;
    }

    fn update_physics(&mut self, context: &UpdateContext) {
        self.acceleration = AngularAcceleration::new::<radian_per_second_squared>(
            self.torque_sum.get::<newton_meter>() / self.propeller_inertia,
        );
        self.speed += AngularVelocity::new::<radian_per_second>(
            self.acceleration.get::<radian_per_second_squared>() * context.delta_as_secs_f64(),
        );
        self.position += Angle::new::<radian>(
            self.speed.get::<radian_per_second>() * context.delta_as_secs_f64(),
        );

        // Reset torque accumulator at end of update
        self.torque_sum = Torque::default();

        self.position = Angle::new::<degree>(self.position.get::<degree>() % 360.);
    }

    pub fn update(&mut self, context: &UpdateContext, stow_pos: Ratio, resistant_torque: Torque) {
        //Only update if propeller is after mechanical pin position
        if stow_pos.get::<ratio>() > Self::PIN_BLADE_UNLOCK_POSITION_WITH_STOW_RATIO {
            self.update_tip_speed_ratio(context);
            self.update_generated_torque(context);
            self.update_friction_torque(resistant_torque);
            self.update_physics(context);
        }
    }
}
impl SimulationElement for WindTurbine {
    fn write(&self, writer: &mut SimulatorWriter) {
        writer.write(&self.rpm_id, self.speed());

        writer.write(&self.angular_position_id, self.position.get::<degree>());

        writer.write(
            &self.propeller_angle_id,
            1. - self.propeller_beta_pitch.output().get::<ratio>(),
        );
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::electrical;
    use crate::hydraulic;
    use crate::shared::update_iterator::MaxStepLoop;
    use crate::simulation::test::{SimulationTestBed, TestBed};
    use crate::simulation::{Aircraft, SimulationElement, SimulationElementVisitor};
    use std::time::Duration;

    use uom::si::{
        angular_velocity::{radian_per_second, revolution_per_minute},
        length::meter,
        mass_density::slug_per_cubic_foot,
        power::kilowatt,
        power::watt,
        thermodynamic_temperature::degree_celsius,
        torque::newton_meter,
        velocity::knot,
    };

    struct TestTorqueLoad {
        current_load: LowPassFilter<Torque>,
    }
    impl TestTorqueLoad {
        fn default() -> Self {
            Self {
                current_load: LowPassFilter::new(Duration::from_millis(300)),
            }
        }

        fn update(&mut self, context: &UpdateContext, load: Power, turbine_speed: AngularVelocity) {
            if turbine_speed.get::<revolution_per_minute>() > 3000. {
                self.current_load
                    .update(context.delta(), Self::resistant_toque(load, turbine_speed));
            } else {
                self.current_load.reset(Torque::default());
            }
        }

        fn resistant_toque(load: Power, turbine_speed: AngularVelocity) -> Torque {
            Torque::new::<newton_meter>(
                -load.get::<watt>() / turbine_speed.get::<radian_per_second>(),
            )
        }

        fn torque(&self) -> Torque {
            self.current_load.output()
        }
    }

    struct TestAircraft {
        updater_max_step: MaxStepLoop,

        turbine: WindTurbine,

        power_load: Power,

        torque_load: TestTorqueLoad,

        stow_position: Ratio,
    }
    impl TestAircraft {
        fn new(turbine: WindTurbine) -> Self {
            Self {
                updater_max_step: MaxStepLoop::new(Duration::from_millis(10)),

                turbine,

                power_load: Power::default(),

                torque_load: TestTorqueLoad::default(),

                stow_position: Ratio::default(),
            }
        }

        fn set_power_load(&mut self, load: Power) {
            self.power_load = load;
        }

        fn set_stow_position(&mut self, position: Ratio) {
            self.stow_position = position;
        }

        fn turbine_rpm(&self) -> f64 {
            self.turbine.speed().get::<revolution_per_minute>()
        }
    }
    impl Aircraft for TestAircraft {
        fn update_after_power_distribution(&mut self, context: &UpdateContext) {
            self.updater_max_step.update(context);

            for cur_time_step in &mut self.updater_max_step {
                self.torque_load.update(
                    &context.with_delta(cur_time_step),
                    self.power_load,
                    self.turbine.speed(),
                );

                self.turbine.update(
                    &context.with_delta(cur_time_step),
                    self.stow_position,
                    self.torque_load.torque(),
                );

                println!(
                    "Air speed={:.0}kts, Turb RPM={:.0} Power load target={:.0}W Torqueload={:.1}",
                    context.true_airspeed().get::<knot>(),
                    self.turbine.speed().get::<revolution_per_minute>(),
                    self.power_load.get::<watt>(),
                    self.torque_load.torque().get::<newton_meter>()
                );
            }
        }
    }
    impl SimulationElement for TestAircraft {
        fn accept<V: SimulationElementVisitor>(&mut self, visitor: &mut V) {
            self.turbine.accept(visitor);

            visitor.visit(self);
        }
    }

    #[test]
    fn do_not_turn_if_stow_pin_locked() {
        let mut test_bed = SimulationTestBed::new(|context| {
            let turbine = a380_wind_turbine(context);
            TestAircraft::new(turbine)
        });

        test_bed.set_true_airspeed(Velocity::new::<knot>(340.));
        test_bed.command(|a| a.set_stow_position(Ratio::new::<ratio>(0.)));
        test_bed.command(|a| a.set_power_load(Power::new::<kilowatt>(70.)));

        test_bed.run_with_delta(Duration::from_secs_f64(1.));

        assert!(test_bed.query(|a| a.turbine_rpm()).abs() < 0.1);

        test_bed.command(|a| {
            a.set_stow_position(Ratio::new::<ratio>(
                WindTurbine::PIN_BLADE_UNLOCK_POSITION_WITH_STOW_RATIO - 0.01,
            ))
        });
        test_bed.run_with_delta(Duration::from_secs_f64(1.));

        assert!(test_bed.query(|a| a.turbine_rpm()).abs() < 0.1);
    }

    /// A380 turbine TESTS
    #[test]
    fn a380_turbine_spins_in_high_air_speed_conditions() {
        let mut test_bed = SimulationTestBed::new(|context| {
            let turbine = a380_wind_turbine(context);
            TestAircraft::new(turbine)
        });

        test_bed.set_true_airspeed(Velocity::new::<knot>(340.));
        test_bed.set_ambient_air_density(MassDensity::new::<slug_per_cubic_foot>(0.002367190));
        test_bed.set_ambient_temperature(ThermodynamicTemperature::new::<degree_celsius>(20.));
        test_bed.command(|a| a.set_stow_position(Ratio::new::<ratio>(1.)));
        test_bed.command(|a| a.set_power_load(Power::new::<kilowatt>(70.)));

        test_bed.run_with_delta(Duration::from_secs_f64(5.));

        assert!(test_bed.query(|a| a.turbine_rpm()) > 3500.);
    }

    #[test]
    fn a380_turbine_spins_in_medium_air_speed_conditions() {
        let mut test_bed = SimulationTestBed::new(|context| {
            let turbine = a380_wind_turbine(context);
            TestAircraft::new(turbine)
        });

        test_bed.set_true_airspeed(Velocity::new::<knot>(200.));
        test_bed.set_ambient_air_density(MassDensity::new::<slug_per_cubic_foot>(0.002367190));
        test_bed.set_ambient_temperature(ThermodynamicTemperature::new::<degree_celsius>(20.));
        test_bed.command(|a| a.set_stow_position(Ratio::new::<ratio>(1.)));
        test_bed.command(|a| a.set_power_load(Power::new::<kilowatt>(70.)));

        test_bed.run_with_delta(Duration::from_secs_f64(5.));
        assert!(test_bed.query(|a| a.turbine_rpm()) > 3500.);
    }

    #[test]
    fn a380_turbine_fails_to_spin_in_min_air_speed_conditions_over_rated_load() {
        let mut test_bed = SimulationTestBed::new(|context| {
            let turbine = a380_wind_turbine(context);
            TestAircraft::new(turbine)
        });

        test_bed.set_true_airspeed(Velocity::new::<knot>(120.));
        test_bed.set_ambient_air_density(MassDensity::new::<slug_per_cubic_foot>(0.002367190));
        test_bed.set_ambient_temperature(ThermodynamicTemperature::new::<degree_celsius>(20.));
        test_bed.command(|a| a.set_stow_position(Ratio::new::<ratio>(1.)));

        // Cannot supply 70KW of power at lowest approach speed
        test_bed.command(|a| a.set_power_load(Power::new::<kilowatt>(70.)));

        test_bed.run_with_delta(Duration::from_secs_f64(5.));
        assert!(test_bed.query(|a| a.turbine_rpm()) < 3200.);
    }

    #[test]
    fn a380_turbine_spins_in_min_air_speed_conditions_at_lower_load() {
        let mut test_bed = SimulationTestBed::new(|context| {
            let turbine = a380_wind_turbine(context);
            TestAircraft::new(turbine)
        });

        test_bed.set_true_airspeed(Velocity::new::<knot>(120.));
        test_bed.set_ambient_air_density(MassDensity::new::<slug_per_cubic_foot>(0.002367190));
        test_bed.set_ambient_temperature(ThermodynamicTemperature::new::<degree_celsius>(20.));
        test_bed.command(|a| a.set_stow_position(Ratio::new::<ratio>(1.)));

        // 58KvA is max rated power output @ 140 kts
        test_bed.command(|a| a.set_power_load(Power::new::<kilowatt>(58.)));

        test_bed.run_with_delta(Duration::from_secs_f64(5.));
        assert!(test_bed.query(|a| a.turbine_rpm()) > 3200.);
    }

    #[test]
    fn a380_turbine_stalling_air_speed_conditions() {
        let mut test_bed = SimulationTestBed::new(|context| {
            let turbine = a380_wind_turbine(context);
            TestAircraft::new(turbine)
        });

        test_bed.set_true_airspeed(Velocity::new::<knot>(50.));
        test_bed.set_ambient_air_density(MassDensity::new::<slug_per_cubic_foot>(0.002367190));
        test_bed.set_ambient_temperature(ThermodynamicTemperature::new::<degree_celsius>(20.));
        test_bed.command(|a| a.set_stow_position(Ratio::new::<ratio>(1.)));
        test_bed.command(|a| a.set_power_load(Power::new::<kilowatt>(70.)));

        test_bed.run_with_delta(Duration::from_secs_f64(5.));

        assert!(test_bed.query(|a| a.turbine_rpm()) < 500.);
    }

    #[test]
    fn a380_turbine_stops_from_full_speed_less_than_5_s_more_than_2s() {
        let mut test_bed = SimulationTestBed::new(|context| {
            let turbine = a380_wind_turbine(context);
            TestAircraft::new(turbine)
        });

        test_bed.set_true_airspeed(Velocity::new::<knot>(340.));
        test_bed.set_ambient_air_density(MassDensity::new::<slug_per_cubic_foot>(0.002367190));
        test_bed.set_ambient_temperature(ThermodynamicTemperature::new::<degree_celsius>(20.));
        test_bed.command(|a| a.set_stow_position(Ratio::new::<ratio>(1.)));

        test_bed.command(|a| a.set_power_load(Power::new::<kilowatt>(40.)));

        test_bed.run_with_delta(Duration::from_secs_f64(5.));
        assert!(test_bed.query(|a| a.turbine_rpm()) > 3800.);

        test_bed.set_true_airspeed(Velocity::default());

        test_bed.run_with_delta(Duration::from_secs_f64(2.));
        assert!(test_bed.query(|a| a.turbine_rpm()) > 200.);

        test_bed.run_with_delta(Duration::from_secs_f64(3.));
        assert!(test_bed.query(|a| a.turbine_rpm()) < 5.);
    }

    #[test]
    fn a380_turbine_stops_from_full_speed_if_wind_less_than_30kts() {
        let mut test_bed = SimulationTestBed::new(|context| {
            let turbine = a380_wind_turbine(context);
            TestAircraft::new(turbine)
        });

        test_bed.set_true_airspeed(Velocity::new::<knot>(340.));
        test_bed.set_ambient_air_density(MassDensity::new::<slug_per_cubic_foot>(0.002367190));
        test_bed.set_ambient_temperature(ThermodynamicTemperature::new::<degree_celsius>(20.));
        test_bed.command(|a| a.set_stow_position(Ratio::new::<ratio>(1.)));

        test_bed.command(|a| a.set_power_load(Power::new::<kilowatt>(40.)));

        test_bed.run_with_delta(Duration::from_secs_f64(5.));
        assert!(test_bed.query(|a| a.turbine_rpm()) > 3800.);

        test_bed.set_true_airspeed(Velocity::new::<knot>(30.));

        test_bed.run_with_delta(Duration::from_secs_f64(10.));
        assert!(test_bed.query(|a| a.turbine_rpm()) < 50.);
    }

    /// A320 turbine TESTS
    #[test]
    fn a320_turbine_spins_in_high_air_speed_conditions() {
        let mut test_bed = SimulationTestBed::new(|context| {
            let turbine = a320_wind_turbine(context);
            TestAircraft::new(turbine)
        });

        test_bed.set_true_airspeed(Velocity::new::<knot>(340.));
        test_bed.set_ambient_air_density(MassDensity::new::<slug_per_cubic_foot>(0.002367190));
        test_bed.set_ambient_temperature(ThermodynamicTemperature::new::<degree_celsius>(20.));
        test_bed.command(|a| a.set_stow_position(Ratio::new::<ratio>(1.)));
        test_bed.command(|a| a.set_power_load(Power::new::<kilowatt>(20.)));

        test_bed.run_with_delta(Duration::from_secs_f64(5.));

        assert!(test_bed.query(|a| a.turbine_rpm()) > 4000.);
    }

    #[test]
    fn a320_turbine_spins_in_medium_air_speed_conditions() {
        let mut test_bed = SimulationTestBed::new(|context| {
            let turbine = a320_wind_turbine(context);
            TestAircraft::new(turbine)
        });

        test_bed.set_true_airspeed(Velocity::new::<knot>(200.));
        test_bed.set_ambient_air_density(MassDensity::new::<slug_per_cubic_foot>(0.002367190));
        test_bed.set_ambient_temperature(ThermodynamicTemperature::new::<degree_celsius>(20.));
        test_bed.command(|a| a.set_stow_position(Ratio::new::<ratio>(1.)));
        test_bed.command(|a| a.set_power_load(Power::new::<kilowatt>(20.)));

        test_bed.run_with_delta(Duration::from_secs_f64(5.));
        assert!(test_bed.query(|a| a.turbine_rpm()) > 4000.);
    }

    #[test]
    fn a320_turbine_fails_to_spin_in_min_air_speed_conditions_over_rated_load() {
        let mut test_bed = SimulationTestBed::new(|context| {
            let turbine = a320_wind_turbine(context);
            TestAircraft::new(turbine)
        });

        test_bed.set_true_airspeed(Velocity::new::<knot>(120.));
        test_bed.set_ambient_air_density(MassDensity::new::<slug_per_cubic_foot>(0.002367190));
        test_bed.set_ambient_temperature(ThermodynamicTemperature::new::<degree_celsius>(20.));
        test_bed.command(|a| a.set_stow_position(Ratio::new::<ratio>(1.)));

        test_bed.command(|a| a.set_power_load(Power::new::<kilowatt>(20.)));

        test_bed.run_with_delta(Duration::from_secs_f64(5.));
        assert!(test_bed.query(|a| a.turbine_rpm()) < 4000.);
    }

    #[test]
    fn a320_turbine_spins_in_min_air_speed_conditions_at_lower_load() {
        let mut test_bed = SimulationTestBed::new(|context| {
            let turbine = a320_wind_turbine(context);
            TestAircraft::new(turbine)
        });

        test_bed.set_true_airspeed(Velocity::new::<knot>(120.));
        test_bed.set_ambient_air_density(MassDensity::new::<slug_per_cubic_foot>(0.002367190));
        test_bed.set_ambient_temperature(ThermodynamicTemperature::new::<degree_celsius>(20.));
        test_bed.command(|a| a.set_stow_position(Ratio::new::<ratio>(1.)));

        test_bed.command(|a| a.set_power_load(Power::new::<kilowatt>(5.)));

        test_bed.run_with_delta(Duration::from_secs_f64(5.));
        assert!(test_bed.query(|a| a.turbine_rpm()) > 3000.);
    }

    #[test]
    fn a320_turbine_stalling_air_speed_conditions() {
        let mut test_bed = SimulationTestBed::new(|context| {
            let turbine = a320_wind_turbine(context);
            TestAircraft::new(turbine)
        });

        test_bed.set_true_airspeed(Velocity::new::<knot>(50.));
        test_bed.set_ambient_air_density(MassDensity::new::<slug_per_cubic_foot>(0.002367190));
        test_bed.set_ambient_temperature(ThermodynamicTemperature::new::<degree_celsius>(20.));
        test_bed.command(|a| a.set_stow_position(Ratio::new::<ratio>(1.)));
        test_bed.command(|a| a.set_power_load(Power::new::<kilowatt>(20.)));

        test_bed.run_with_delta(Duration::from_secs_f64(5.));

        assert!(test_bed.query(|a| a.turbine_rpm()) < 500.);
    }

    #[test]
    fn a320_turbine_stops_from_full_speed_less_than_5_s_more_than_2s() {
        let mut test_bed = SimulationTestBed::new(|context| {
            let turbine = a320_wind_turbine(context);
            TestAircraft::new(turbine)
        });

        test_bed.set_true_airspeed(Velocity::new::<knot>(340.));
        test_bed.set_ambient_air_density(MassDensity::new::<slug_per_cubic_foot>(0.002367190));
        test_bed.set_ambient_temperature(ThermodynamicTemperature::new::<degree_celsius>(20.));
        test_bed.command(|a| a.set_stow_position(Ratio::new::<ratio>(1.)));

        test_bed.command(|a| a.set_power_load(Power::new::<kilowatt>(20.)));

        test_bed.run_with_delta(Duration::from_secs_f64(3.));
        assert!(test_bed.query(|a| a.turbine_rpm()) > 5000.);

        test_bed.set_true_airspeed(Velocity::default());

        test_bed.run_with_delta(Duration::from_secs_f64(2.));
        assert!(test_bed.query(|a| a.turbine_rpm()) > 200.);

        test_bed.run_with_delta(Duration::from_secs_f64(5.));
        assert!(test_bed.query(|a| a.turbine_rpm()) < 5.);
    }

    #[test]
    fn a320_turbine_stops_from_full_speed_if_wind_less_than_30kts() {
        let mut test_bed = SimulationTestBed::new(|context| {
            let turbine = a320_wind_turbine(context);
            TestAircraft::new(turbine)
        });

        test_bed.set_true_airspeed(Velocity::new::<knot>(340.));
        test_bed.set_ambient_air_density(MassDensity::new::<slug_per_cubic_foot>(0.002367190));
        test_bed.set_ambient_temperature(ThermodynamicTemperature::new::<degree_celsius>(20.));
        test_bed.command(|a| a.set_stow_position(Ratio::new::<ratio>(1.)));

        test_bed.command(|a| a.set_power_load(Power::new::<kilowatt>(20.)));

        test_bed.run_with_delta(Duration::from_secs_f64(5.));
        assert!(test_bed.query(|a| a.turbine_rpm()) > 5000.);

        test_bed.set_true_airspeed(Velocity::new::<knot>(30.));

        test_bed.run_with_delta(Duration::from_secs_f64(10.));
        assert!(test_bed.query(|a| a.turbine_rpm()) < 50.);
    }

    fn a380_wind_turbine(context: &mut InitContext) -> WindTurbine {
        WindTurbine::new(
            context,
            Length::new::<meter>(electrical::RamAirTurbine::PROPELLER_DIAMETER_M / 2.),
            electrical::RamAirTurbine::RPM_GOVERNOR_BREAKPTS,
            electrical::RamAirTurbine::PROP_ALPHA_MAP,
            electrical::RamAirTurbine::DYNAMIC_FRICTION,
            electrical::RamAirTurbine::BEST_EFFICIENCY_TIP_SPEED_RATIO,
            electrical::RamAirTurbine::PROPELLER_INERTIA,
        )
    }

    fn a320_wind_turbine(context: &mut InitContext) -> WindTurbine {
        WindTurbine::new(
            context,
            Length::new::<meter>(hydraulic::RamAirTurbine::PROPELLER_DIAMETER_M / 2.),
            hydraulic::RamAirTurbine::RPM_GOVERNOR_BREAKPTS,
            hydraulic::RamAirTurbine::PROP_ALPHA_MAP,
            hydraulic::RamAirTurbine::DYNAMIC_FRICTION,
            hydraulic::RamAirTurbine::BEST_EFFICIENCY_TIP_SPEED_RATIO,
            hydraulic::RamAirTurbine::PROPELLER_INERTIA,
        )
    }
}
